/*
 * User.java
 *
 * Created on July 5, 2005, 10:58 PM
 *
 * To change this basis, choose Tools | Options and locate the basis under
 * the Source Creation and Management node. Right-click the basis and choose
 * Open. You can then make changes to the basis in the Source Editor.
 */

package com.xucia.jsponic.security;

import static com.xucia.jsponic.data.GlobalData.ACCESS_FIELD;
import static com.xucia.jsponic.data.GlobalData.BASIS_FIELD;
import static com.xucia.jsponic.data.GlobalData.IN_GROUPS_FIELD;
import static com.xucia.jsponic.data.GlobalData.PASSWORD_FIELD;
import static com.xucia.jsponic.data.GlobalData.PERMISSION_LEVEL_FIELD;
import static com.xucia.jsponic.data.GlobalData.ROLE_FIELD;
import static com.xucia.jsponic.data.GlobalData.USER_DATA_FIELD;
import static com.xucia.jsponic.data.GlobalData.USER_Id_FIELD;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.security.PrivilegedAction;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.logging.Logger;

import javax.security.auth.login.LoginException;

import org.apache.commons.codec.binary.Base64;
import org.mozilla.javascript.NativeArray;
import org.mozilla.javascript.NativeObject;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;

import com.xucia.jsponic.JsponicRuntime;
import com.xucia.jsponic.data.DataSourceManager;
import com.xucia.jsponic.data.GlobalData;
import com.xucia.jsponic.data.Persistable;
import com.xucia.jsponic.data.PersistableImpl;
import com.xucia.jsponic.data.PersistentList;
import com.xucia.jsponic.data.JsonPath;
import com.xucia.jsponic.data.SimpleStipulation;
import com.xucia.jsponic.data.Stipulation;
import com.xucia.jsponic.data.Templates;
/**
 *
 * There are three states of a user:
 * 1. anonymous private - user is maintained across sessions through cookies and can be converted to a real user by establishing a username and password.  This is the default state.
 * 2. shared public - multiple users are using this computer so a user is not maintained across sessions
 * 3. authenticated - either one or two can convert to this, but if a user already exists, it must do a merge of data
 */
public class User extends NativeObject implements Principal {
    public static final String PUBLIC_USER_NAME="_public_";
    //TODO: make this not public!
    public Map<Persistable,PermissionLevel> computedPermissions = new WeakHashMap();
    boolean isSharedPublic = false;
    public static Map<Thread,User> threadUserMap = new HashMap();
    private static User publicUserObject;  
    static Persistable securitySettings;
    static java.security.acl.Group supervisorGroup;
    public static void resetSecurity() {
    	doPriviledgedAction(new PrivilegedAction() {

			public Object run() {
				Object securitySettingsObject = DataSourceManager.getRootObject().get("securitySettings");
				Object supervisorGroupObject = ScriptableObject.NOT_FOUND;
				if (securitySettingsObject instanceof Persistable) {
			    	securitySettings = (Persistable) securitySettingsObject;
					//publicUserObject = (Persistable) getSecuritySettings().get("anonymousUser");
			    	supervisorGroupObject = getSecuritySettings().get("supervisor");
				}
		    	if (supervisorGroupObject==ScriptableObject.NOT_FOUND)
		    		supervisorGroup = new Anyone();
		    	else
		    		supervisorGroup = (java.security.acl.Group) supervisorGroupObject;
		    	return null;
			}
    		
    	});
    	
    }
    static {
    	resetSecurity();
    }
    long id;

    public User(final String username, final String password) {
        //id = getNewId();
    	//findUser("" + id);
    	doPriviledgedAction(new PrivilegedAction() {

			public Object run() {
				validateUsername(username);
				setupNewUser(true);
				setUsername(username);
				setPassword(password);
				return null;
			}
    	});
    }

    public User() {
    }
    private User(boolean isShared) {
    	this.isSharedPublic = isShared;
    }
    public static User PUBLIC_USER = new User(true);
    private static Persistable getSecuritySettings() {
    	return securitySettings;
    }
    public static java.security.acl.Group getSupervisorGroup() {
    	return (java.security.acl.Group) supervisorGroup;
    	
    }
    private User findUser(String id) {
        List correctUsers = (List) JsonPath.query("#[?(@.userid=#)]",usersTable(),id); 
        	//PersistentList.filter(usersTable(),new SimpleStipulation(USER_Id_FIELD,"" + id));
        //System.err.println("correctUsers = " + correctUsers.isEmpty());
        if (correctUsers.isEmpty())
        	return publicUserObject;
        else
        	return (User) correctUsers.get(0);
    }
    private void setupNewUser(final boolean realUser) {
		        usersTable().add(User.this);
		        boolean registerAsNewUser = true;
//		        userObject = (Persistable) usersTable().get(usersTable().size()-1);
		    	if (realUser) {
			        //calculateMembership(userObject, usersTable());
			      //  userObject.put(USER_Id_FIELD, id);
			        //if (usersTable().get(GlobalData.PERMISSION_FIELD) instanceof DataSourceObject)
			           // userObject.put(GlobalData.PERMISSION_FIELD, usersTable().get(GlobalData.PERMISSION_FIELD));
			        if (Scriptable.NOT_FOUND==getSecuritySettings().get("supervisor")) {
			        	supervisorGroup = new Group();
			        	((Persistable) supervisorGroup).set(GlobalData.BASIS_FIELD, Templates.getBasisForClass(Group.class));
			        	getSecuritySettings().set("supervisor",supervisorGroup);
			        	getSupervisorGroup().addMember(User.this);
			        	registerAsNewUser = true;
			        	
			            //calculateMembership(User.this, getSupervisorGroup());
			        	//getSecuritySettings().delete("nextCreatedUserIsSupervisor");
			        }
		    	}
		        set(BASIS_FIELD, Templates.getBasisForClass(User.class));
		        Persistable userData = (Persistable) set(USER_DATA_FIELD, JsponicRuntime.newArray());
		        List security =(List) userData.set(ACCESS_FIELD, JsponicRuntime.newArray());
		        ((Persistable) security).set(GlobalData.NAME_FIELD, getName() + "'s");
		        //DataObjectList list = (DataObjectList) security.put(ROLE_PERMITS_FIELD, new TransientDataObjectList());
		        Persistable rolePermit = JsponicRuntime.newObject();
		        //TODO: Change this to get the permit by level number
		        Object writeLevel = ((List) JsonPath.query("select * from ? where name='write'",(List) securitySettings.get("permissionLevelTypes"))).get(0);
		        rolePermit.set(PERMISSION_LEVEL_FIELD, writeLevel);
		       /* rolePermit.put(VERSION_FIELD, Boolean.TRUE);
		        rolePermit.put(APPEND_FIELD, Boolean.TRUE);
		        rolePermit.put(READ_FIELD, Boolean.TRUE);
		        rolePermit.put(WRITE_FIELD, Boolean.TRUE);*/
		        rolePermit.set(ROLE_FIELD, User.this);
		        security.add(rolePermit);

    }

    static long getNewId() {
        // TODO: we need to do something ensure that this does not return a value that had been used before, it is also more efficient if it is hex format
        return (new Random()).nextLong();
    }
    @Deprecated
    public static User publicUser() {
        return PUBLIC_USER; 
    }
/*    DataObject getUserObject() {
        DataObjectList correctUsers = (DataObjectList) usersTable.get(new SimpleStipulation("userid","" + id));
        if (correctUsers.size() == 0) {
            throw new LoginException("operating on a user with no valid entry in the database");
        }
        return (DataObject) correctUsers.get(0);
    }*/
    /*User(Persistable userObject ) {
    	addActiveUser();
        this.userObject = userObject;
        //id = Long.parseLong((String) userObject.get(USER_Id_FIELD));
        if (userObject.get(GlobalData.NAME_FIELD) instanceof String)
            username = (String) userObject.get(GlobalData.NAME_FIELD);
      //  else
        //    username = "" + id;
    }*/
    String currentTicket;
    public String getCurrentTicket() {
    	return currentTicket;
    }
    public static User getUserByTicket(String id,String ipAddress) throws LoginException {
        if (PUBLIC_USER_NAME.equals(id))
            return publicUser();
    	final String ticket = id + ipAddress;
    	Object userObject = doPriviledgedAction(new PrivilegedAction() {
    		public Object run() {
    			Object userTickets = DataSourceManager.getRootObject().get("userTickets");
    			if (!(userTickets instanceof Persistable)) {
    				userTickets = JsponicRuntime.newObject();
    				DataSourceManager.getRootObject().set("userTickets",userTickets);
    			}
    					
				return ((Persistable) userTickets).get(ticket);
			}		 
    	});
    	
    	if (userObject instanceof User) {
    		return (User) userObject;
    	}
    	else 
    		throw new LoginException("The user ticket is no longer valid");
    }
    @Deprecated
    public String getNewTicket(String ipAddress) {
    	String ticket = ipAddress + new Date().getTime() + "" + new Random().nextInt();
    	((Persistable) DataSourceManager.getRootObject().get("userTickets")).set(ticket,this);
    	return ticket;
    }
    public static User getUserByUsername(String username) {
    	if (username.equals(currentUser().getName()))
    		return currentUser();
    	if (getSupervisorGroup().isMember(currentUser()))
    		throw new RuntimeException("You do have access to this user.  To access other users, you must be a system supervisor");
        List correctUsers = (List) JsonPath.query("select * from ? where name=?", usersTable(),username);
        if (correctUsers.size() == 0) {
            throw new RuntimeException("user " + username + " not found");
        }
    	return (User) ((Persistable) correctUsers.get(0));
    		
    }
    /* merge the data into our data */
    void mergeUserData(User user) {
        //TODO: Implement this
    //    userObject.putAll(user.userObject);
      //  userObject.put(GlobalData.NAME_FIELD,username);
    }
    public static User authenticate(final String username,final String password) throws LoginException {
    	Object result = doPriviledgedAction(new PrivilegedAction() {
    		public Object run() {
    			List correctUsers = (List) JsonPath.query("select * from ? where name=?", usersTable(),username);
    	        if (correctUsers.size() == 0) {
    	            return new LoginException("user " + username + " not found");
    	        }
    	        Persistable userObject = (Persistable) correctUsers.get(0);
    	        boolean alreadyHashed = false;
    	        boolean passwordMatch = password.equals(userObject.get(PASSWORD_FIELD));
    	        if (!passwordMatch) {
    	            try {
    				MessageDigest md = MessageDigest.getInstance("SHA");
    				md.update(((String) userObject.get(PASSWORD_FIELD)).getBytes());
    				passwordMatch = password.equals(new String(new Base64().encode(md.digest())));
    				} catch (NoSuchAlgorithmException e) {
    					throw new RuntimeException(e);
    				}
    				alreadyHashed = true;        	
    	        }
    	        if (passwordMatch) {
    	            Logger.getLogger(User.class.toString()).info("User " + username + " has been authenticated");
    	            User user = (User) userObject;
    	            try {
    	            	if (alreadyHashed)
    	            		user.currentTicket = password;
    	            	else {
    						MessageDigest md = MessageDigest.getInstance("SHA");
    						md.update(password.getBytes());
    						user.currentTicket = new String(new Base64().encode(md.digest()));
    	            	}
    				} catch (NoSuchAlgorithmException e) {
    					throw new RuntimeException(e);
    				}
    	            return user;
    	        }
    	        else {
    	            Logger.getLogger(User.class.toString()).info("The password was incorrect for " + username);
    	            return new LoginException("The password was incorrect for user " + username + ". ");
    	        }
    			
    		}
    	});
    	if (result instanceof LoginException)
    		throw (LoginException) result;
    	return (User) result;
    }
/*    public User reAuthenticate(String username,String password) throws LoginException{
        User user = authenticate(username,password);
        if (getName() == null)  // merge the current data only if he is not another named user
            user.mergeUserData(this);
        return user;
    }*/
    static boolean hostSpecificSecurity = false;
    protected static List usersTable() {
    	if (hostSpecificSecurity)
    		return (List) ((Persistable) getWebsiteSourceObject().get("settings")).get("allUsersGroup");
    	else {
			return (List) doPriviledgedAction(new PrivilegedAction() {
	    		public Object run() {
		    		Object usersObject = securitySettings.get("users");
		    		if (usersObject == ScriptableObject.NOT_FOUND)
		    			return securitySettings.set("users",JsponicRuntime.newArray());
		    		else
		    			return (List) usersObject;
	    		}
	    	});
    	}
    }
    //private String username = null;
   // private Persistable userObject = null;
//    private String password = null;
/*    public Persistable getUserObject() {
    	return userObject;
    }*/
    public void logout() {
      //  username = null;
    }
    public String getName() {
    	Object nameObject = get(GlobalData.NAME_FIELD);
    	if (nameObject instanceof String)
    		return (String) nameObject;
    	return null;
    }
    public String getPassword() {
    	return (String) get(GlobalData.PASSWORD_FIELD);
    }
    public static final Persistable PRIVILEDGED_USER_OBJECT = JsponicRuntime.newObject();
    public static final String EVERYONE = "everyone";
    public static final Object SUPER_ROLE = new Object();
    /*public boolean isUserInRole(final Object role) {
        // TODO: Put this in the same package as AbstractWrapper so we can go through to the inner data to do this check
/*        while (role instanceof DataObject && !(role instanceof DataSourceObject)) {
            role = ((AbstractWrapper) role).data();
        }
        //System.err.println("testisUserInRole " + role + " userObject " + userObject + " == " + (role == userObject));
        if (role == userObject)
            return true;
        if (role.equals(EVERYONE))
            return true;
        if (userObject == null)
            return false;
        if (role.equals("authenticated"))
            return true; // TODO: I would like to get rid of these two for optimization
        if (userObject.equals(PRIVILEDGED_USER_OBJECT))
        	return true;
/*        if (username != null && username.equals(role)) // Your user name counts as a role
            return true; 
        //Object userInfo = userObject.get(USER_INFO_FIELD);
        if (role instanceof Persistable) {
            // assuming it is a group now
        	return Boolean.TRUE.equals(doPriviledgedAction(new PrivilegedAction<Boolean>() {

    			public Boolean run() {

    				List roleMatch = PersistentList.filter((List) userObject.get(IN_GROUPS_FIELD),new IsStipulation(role));
    	            return !roleMatch.isEmpty();
    			}
    		}));
/*            Object users = ((DataObject) role).get(USERS_FIELD);
            if (users instanceof DataObjectList)
                return !((DataObjectList) users).get(new IsStipulation(userObject)).isEmpty();
            else
                System.err.println("specified role is not a group " + role); */
        //}
/*        if (userInfo instanceof DataObject)
            return (((DataObjectList) ((DataObject) userInfo).get("groups")).get(new SimpleStipulation("name", role)).size() > 0);
        return false;
    }*/
    void validateUsername(String username) {
    	List correctUsers = (List) JsonPath.query("select * from ? where name=?", usersTable(),username);
        if (correctUsers.size() != 0) 
            throw new RuntimeException("The username " + username + " is already taken");
    }
    public void setUsername(String username) {
    	List correctUsers = (List) JsonPath.query("select * from ? where name=?", usersTable(),username);
    	if (correctUsers.size() == 0) {
            set(GlobalData.NAME_FIELD,username);
            //this.username = username;
        }
        else
            throw new RuntimeException("The username " + username + " is already taken");
    }
    /* login as a different user, must be in the su capable group */
    public User su(String username) {
        if (!getSupervisorGroup().isMember(this))
            throw new RuntimeException("You are not a part of the su capable group, so you can not su");
        List correctUsers = (List) JsonPath.query("select * from ? where name=?", usersTable(),username);
        if (correctUsers.size() == 0) {
            throw new RuntimeException("user " + username + " not found");
        }
        Persistable userObject = (Persistable) correctUsers.get(0);
        return (User) userObject;
    }
    public void setPassword(String password) {
        Logger.getLogger(User.class.toString()).info("now changing the password for " + getName());
        set(PASSWORD_FIELD,password);
    }
    static String encrypt(String plaintext)
    {
         MessageDigest d =null;
         try {
         d = MessageDigest.getInstance("SHA-1");
         //d.reset();
         d.update(plaintext.getBytes("UTF-8"));
         }
         catch(Exception e) {
             e.printStackTrace();
         }
         return new String(Base64.encodeBase64(d.digest()));
    }
    private static boolean oneWayCompare(Collection c1, Collection c2)
    {
    	for (Object obj : c1)
    		if (!c2.contains(obj))
    			return false;
    	return true;
    }
    private static boolean compare(Collection c1, Collection c2) {
    	return oneWayCompare(c1, c2) && oneWayCompare(c2, c1); 
    }
    public static Set<Persistable> calculateMembership(Persistable member, List groupToConsider) {
    	Logger.getLogger(User.class.toString()).info("calculating membership");
    	Set newInGroups = new HashSet();
    	Set<List>groupsToConsider = new HashSet();
    	if (groupToConsider != null)
    		groupsToConsider.add(groupToConsider);
    	try {
    		groupsToConsider.addAll((List) member.get(IN_GROUPS_FIELD));
    	}
    	catch (ClassCastException e) {
    	}
    	try {
	    	for (List group : groupsToConsider) {
	    		if (group.contains(member)) { // verify membership
	    			newInGroups.addAll(calculateMembership((Persistable) group,null));
	    		}
	    	}
    	}
    	catch (ClassCastException e) {
    		e.printStackTrace();
    	}
    	
    	Object oldInGroups = member.get(IN_GROUPS_FIELD);
    	if (!(oldInGroups instanceof List) ||  !compare((List) oldInGroups,newInGroups)) {
    		NativeArray newInGroupsObject = JsponicRuntime.newArray();
    		for (Object obj : newInGroups)
    			newInGroupsObject.add(obj);
    		member.set(IN_GROUPS_FIELD, newInGroupsObject);
    		for (Persistable subMember : (List<Persistable>) member)
    			calculateMembership(subMember, (List) member);
    	}
    	newInGroups.add(member);  // we return a list of all membership, so I should be included
    	resetComputedPermissions();
    	return newInGroups;
    }

    public void registerThisThread() {
    	threadUserMap.put(Thread.currentThread(),this);
    }
    // Make this not public
    public static User currentUser() { 
    	return threadUserMap.get(Thread.currentThread());
    }

    public int compareTo(Object o) {
        return new Integer(hashCode()).compareTo(new Integer(o.hashCode()));
    }
    
    public static final boolean USERS_HAVE_SEPARATE_SPACE_WITHIN_WEBSITES = false; // TODO: This needs to be a setting in the database
    public PersistableImpl getUserData() {
    	if (this == publicUserObject) {
        	setupNewUser(true);
    	}
    	//Persistent handlerUserObject = (Persistent) getRootObject().getById(userObject.getId());
        //Persistent basisrUserObject = ((Persistent) Persistent.wrapper(PersistentObjectHandler.class,VoidPersistent.rootBasisrContext, userObject));
    	PrivilegedAction<PersistableImpl> action = new PrivilegedAction<PersistableImpl>() {

			public PersistableImpl run() {

		    	Object userDataObject = get(USER_DATA_FIELD);
		    	if (userDataObject == Scriptable.NOT_FOUND)
		    		return null;
		    	PersistableImpl userData = (PersistableImpl) get(USER_DATA_FIELD);
				if (userData.get("inbox") == Scriptable.NOT_FOUND) {
					((Persistable) userData.set("inbox", JsponicRuntime.newObject())).set(GlobalData.CONTAINS_REPEATING_IDENTIFIER,true);
				}
		    	if (USERS_HAVE_SEPARATE_SPACE_WITHIN_WEBSITES) {
			     	PersistableImpl websiteUserData = getOrCreatePersistentObject(userData,getWebsite().getId().toString());
			    	if (websiteUserData.get(GlobalData.PARENT_FIELD) != DataSourceManager.getRootObject())
			    		((PersistableImpl) websiteUserData).set(GlobalData.PARENT_FIELD,DataSourceManager.getRootObject());
			        return (PersistableImpl) websiteUserData;
		    	}
		    	return (PersistableImpl) userData;
			}
    	};
    	if (currentUser() == this)
    		return (PersistableImpl) doPriviledgedAction(action);
    	return action.run();
    }



//TODO: Would it make sense for all of this to go in a separate class?
    public static Map<Thread,String> threadWebsiteMap = new HashMap();
    
    /** This registers which website we are using for this particular user, which affects the users data and possibly the user table that is used */
    public static void registerThisWebsite(String webappContextName) {
    	//System.err.println("registering website: " + webappContextName);
    	threadWebsiteMap .put(Thread.currentThread(),webappContextName);
    	if (!GlobalData.DEFAULT_WEBSITE.equals(webappContextName))
    		getWebsiteSourceObject(); // We do this to determine if we are really in this context name or just in the root
    }
    
   
    public static String getCurrentWebsiteName() { 
    	return threadWebsiteMap .get(Thread.currentThread());
    }
    
    
    
	    static Stipulation websiteStipulation() {
	/*        MultiStipulation multi = new MultiStipulation();
	        multi.operator = MultiStipulation.Operator.OR;*/
	        return new SimpleStipulation("webapp name",getCurrentWebsiteName()); 
	/*        multi.stipulationB = new SimpleStipulation("webapp name","*");
	        return multi; 
	*/    }
	    public static Persistable getWebsite() {
	    	return getWebsiteSourceObject();
	    }
	    public static Persistable getWebsiteSourceObject() {
	        //TODO: Find the correct website based upon the URL
	    	Object website = doPriviledgedAction(new PrivilegedAction() {
	    		public Object run() {
		    		Object websitesObject= DataSourceManager.getRootObject().get("websites");
		    		Persistable websites = null;
		    		try {
		    			websites = (Persistable) websitesObject; 
		    		}
		    		catch (ClassCastException e) {
		    			throw new RuntimeException(e);
		    		}
		    		return websites.get(getCurrentWebsiteName());
	    		}
	    	});
    		if (website instanceof Persistable) 
    			return (Persistable) website;
    		registerThisWebsite(GlobalData.DEFAULT_WEBSITE);
    		return null;//(Persistable) websites.get(GlobalData.DEFAULT_WEBSITE);
	    }
	    public static Persistable getWebsites() {
    		return (Persistable) DataSourceManager.getRootObject().get("websites");
	    }
    
/*	public boolean hasEditorAccess() {
		try {
			if (((Persistent) ((DataObject) getWebsite().get("settings")).get("editorAccess")).isEditable());
				return true;
		}
		catch (SecurityException e) {
		}
		return false;
	}*/
	private static PersistableImpl getOrCreatePersistentObject(Persistable object, String name) {
		Object value = object.get(name);
		if (!(value instanceof PersistableImpl)) {
			value = JsponicRuntime.newObject();
			object.put(name, object, value);
		}
		return (PersistableImpl) value;
	}
	private static PersistentList getOrCreatePersistentList(Persistable object, String name) {
		Object value = object.get(name);
		if (!(value instanceof PersistentList)) {
			value = JsponicRuntime.newArray();
			object.put(name, object, value);
		}
		return (PersistentList) value;
	}
	public PersistableImpl getCurrentEditingVersion() {
		PersistentList editingVersions = getOrCreatePersistentList(getUserData(), "editingVersions");
		PersistableImpl currentVersion = getOrCreatePersistentObject(editingVersions,"currentVersion");
		if (currentVersion == getWebsite())
			return currentVersion;
		if (currentVersion.get(GlobalData.BASIS_FIELD)== Scriptable.NOT_FOUND) {
			currentVersion.set(GlobalData.BASIS_FIELD,Templates.findTemplate("versionBasis"));
		}
		if (currentVersion.get(GlobalData.VERSION_OF_FIELD) == Scriptable.NOT_FOUND) {
			currentVersion.set(GlobalData.VERSION_OF_FIELD,getWebsite());
			currentVersion.set(GlobalData.NAME_FIELD, getName() + "'s version of " + getWebsite().get(GlobalData.NAME_FIELD));
		}
		if (!Boolean.TRUE.equals(editingVersions.get(GlobalData.CONTAINS_REPEATING_IDENTIFIER))) {
			editingVersions.set(GlobalData.BASIS_FIELD, Templates.findTemplate("versionsBasis"));
			editingVersions.add(currentVersion);
			editingVersions.add(currentVersion = (PersistableImpl) getWebsite());
			editingVersions.set("currentVersion", currentVersion);
		}
		// regrab it in case the class changed
		//currentVersion = ((Persistent)getUserData()).getOrCreatePersistentObject("editingVersions").getOrCreatePersistentObject("currentVersion");
		return currentVersion;
	}
	public static class PriviledgedUser extends User {
		User realCurrentUser;
		PriviledgedUser(User currentUser) {
			super(false);
			this.realCurrentUser = currentUser;
		}
		public User getRealCurrentUser() {
			return realCurrentUser;
		}
		public PersistableImpl getUserData() {
			return realCurrentUser.getUserData();
		}
		
	}
	public static abstract class PrivilegedActionUserAware implements PrivilegedAction {
		User currentUser;
		public User getCurrentUser() {
			return currentUser;
		}
	}
	public static Object doPriviledgedAction(PrivilegedAction action){
		PriviledgedUser priviledgedUser = new PriviledgedUser(currentUser());
		if (action instanceof PrivilegedActionUserAware)
			((PrivilegedActionUserAware)action).currentUser = currentUser();
		try {
			priviledgedUser.registerThisThread();
			return action.run();
		}
		finally {
			if (priviledgedUser.realCurrentUser != null)
				priviledgedUser.realCurrentUser.registerThisThread();
		}
	}

	public static boolean isHostSpecificSecurity() {
		return hostSpecificSecurity;
	}

	public static void setHostSpecificSecurity(boolean websiteSpecificSecurity) {
		User.hostSpecificSecurity = websiteSpecificSecurity;
	}
}
